package redis

import (
	"encoding/json"
	"github.com/beego/beego/v2/core/logs"
	"github.com/gomodule/redigo/redis"
	"time"
)

type DataRedis struct {
	RedisHost string
	RedisDB   string
	RedisPwd  string
	Timeout   int64
	RedisPool *redis.Pool
}

func (r *DataRedis) NewPool() *redis.Pool {
	return &redis.Pool{
		Dial:        r.RedisConnect,
		MaxIdle:     10,
		MaxActive:   0,
		IdleTimeout: 1 * time.Second,
		Wait:        true,
	}
}

func (r *DataRedis) RedisConnect() (redis.Conn, error) {
	c, err := redis.Dial("tcp", r.RedisHost)
	if err != nil {
		return nil, err
	}

	//处理密码
	if r.RedisPwd != "" {
		_, err = c.Do("AUTH", r.RedisPwd)
		if err != nil {
			return nil, err
		}
	}

	_, err = c.Do("SELECT", r.RedisDB)
	if err != nil {
		return nil, err
	}

	redis.DialConnectTimeout(time.Duration(r.Timeout) * time.Second)
	redis.DialReadTimeout(time.Duration(r.Timeout) * time.Second)
	redis.DialWriteTimeout(time.Duration(r.Timeout) * time.Second)

	return c, nil
}

// Get Used to set the value of a given key. If the key already stores other values,
// SET overwrites the old value, regardless of the type.
func (r *DataRedis) Get(key string) (string, error) {
	c := r.RedisPool.Get()
	defer c.Close()
	val, err := c.Do("GET", key)

	if err != nil {
		logs.Error("get error", err.Error())
		return "", err
	}

	v, _ := redis.String(val, err)
	return v, nil
}

// Set Used to get the value of the specified key. If the key does not exist, return nil.
// If the value stored in the key is not a string type, an error is returned.
func (r *DataRedis) Set(k, v string) error {
	c := r.RedisPool.Get()
	defer c.Close()
	_, err := c.Do("SET", k, v)
	return err
}

// SetEx The specified key setting value and its expiration time.
// If the key already exists, the SETEX command will replace the old value.
func (r *DataRedis) SetEx(k string, v interface{}, ex int64) error {
	c := r.RedisPool.Get()
	defer c.Close()
	_, err := c.Do("SET", k, v, "EX", ex)
	return err
}

// SetKeyOutTime Set the expiration time of the key.
// After the key expires, it will no longer be available.
// Unit is in seconds.
func (r *DataRedis) SetKeyOutTime(key string, ex int) error {
	c := r.RedisPool.Get()
	defer c.Close()
	_, err := c.Do("EXPIRE", key, ex)
	if err != nil {
		logs.Error("set error", err.Error())
		return err
	}
	return nil
}

// HeCheck 用于查看哈希表的指定`字段`是否存在。
func (r *DataRedis) HeCheck(key string, f interface{}) bool {
	c := r.RedisPool.Get()
	defer c.Close()
	_, err := c.Do("HEXISTS", key, f)
	if err != nil {
		return false
	} else {
		return true
	}
}

// Del 用于删除已存在的键。不存在的 key 会被忽略。
func (r *DataRedis) Del(key string) error {
	c := r.RedisPool.Get()
	defer c.Close()
	_, err := c.Do("DEL", key)
	if err != nil {
		return err
	}
	return nil
}

// SetJson 在指定的 key 不存在时，为 key 设置指定的对象并转换为json字符串保存
func (r *DataRedis) SetJson(key string, data interface{}) error {
	c := r.RedisPool.Get()
	defer c.Close()
	value, _ := json.Marshal(data)
	n, err := c.Do("SETNX", key, value)
	if n != int64(1) {
		return err
	}
	return nil
}

// GetJsonMap 通过key获取json字符串并转化为 map
func (r *DataRedis) GetJsonMap(key string) (map[string]interface{}, error) {
	c := r.RedisPool.Get()
	defer c.Close()

	var jsonData map[string]interface{}
	bv, err := redis.Bytes(c.Do("GET", key))
	if err != nil {
		return nil, err
	}
	errJson := json.Unmarshal(bv, &jsonData)
	if errJson != nil {
		return nil, err
	}
	return jsonData, nil
}

// HSet 用于为哈希表中的字段赋值 。
// 如果哈希表不存在，一个新的哈希表被创建并进行 HSET 操作。
// 如果字段已经存在于哈希表中，旧值将被覆盖。
// 注意 设置什么类型 取的时候需要获取对应类型
func (r *DataRedis) HSet(key string, field string, data interface{}) error {
	c := r.RedisPool.Get()
	defer c.Close()
	_, err := c.Do("HSET", key, field, data)
	if err != nil {
		return err
	}
	return nil
}

// HGet 用于返回哈希表中指定字段的值。
func (r *DataRedis) HGet(key, field string) (interface{}, error) {
	c := r.RedisPool.Get()
	defer c.Close()
	data, err := c.Do("HGET", key, field)
	if err != nil {
		return nil, err
	}
	return data, nil
}

// HGetAll 用于返回哈希表中，所有的字段和值。
// 在返回值里，紧跟每个字段名(field name)之后是字段的值(value)，
// 所以返回值的长度是哈希表大小的两倍。
func (r *DataRedis) HGetAll(key string) (map[string]string, error) {
	c := r.RedisPool.Get()
	defer c.Close()
	data, err2 := redis.StringMap(c.Do("HGETALL", key))
	_, err := data, err2
	if err != nil {
		return nil, err
	}
	return data, nil
}

// LPush 将一个或多个值插入到列表头部。
// 如果 key 不存在，一个空列表会被创建并执行 LPUSH 操作。
// 当 key 存在但不是列表类型时，返回一个错误。
func (r *DataRedis) LPush(key string, val interface{}) error {
	c := r.RedisPool.Get()
	defer c.Close()
	_, err := c.Do("LPUSH", key, val)
	return err
}

// RPush 用于将一个或多个值插入到列表的尾部(最右边)。
// 如果列表不存在，一个空列表会被创建并执行 RPUSH 操作。
// 当列表存在但不是列表类型时，返回一个错误。
func (r *DataRedis) RPush(key, val string) error {
	c := r.RedisPool.Get()
	defer c.Close()
	_, err := c.Do("RPUSH", key, val)
	return err
}

// LPop 用于移除并返回列表的第一个元素。
func (r *DataRedis) LPop(key string) (interface{}, error) {
	c := r.RedisPool.Get()
	defer c.Close()
	res, err := c.Do("LPOP", key)
	return res, err
}

// RPop 用于移除列表的最后一个元素，返回值为移除的元素。
func (r *DataRedis) RPop(key string) (interface{}, error) {
	c := r.RedisPool.Get()
	defer c.Close()
	res, err := c.Do("RPOP", key)
	return res, err
}

// LRem 根据参数 COUNT 的值，移除列表中与参数 VALUE 相等的元素。
// COUNT 的值可以是以下几种：
// count > 0 : 从表头开始向表尾搜索，移除与 VALUE 相等的元素，数量为 COUNT 。
// count < 0 : 从表尾开始向表头搜索，移除与 VALUE 相等的元素，数量为 COUNT 的绝对值。
// count = 0 : 移除表中所有与 VALUE 相等的值。
func (r *DataRedis) LRem(key string) (interface{}, error) {
	c := r.RedisPool.Get()
	defer c.Close()
	res, err := c.Do("LREM", key)
	return res, err
}
